Repository navigation
Conversation
goose 1.39.0 replaced the flat accumulated_input/output_tokens export fields with nested usage and accumulated_usage objects, so calculate-tokens silently reported zero for every session on current goose. Read accumulated_usage first, then usage, then the old flat fields, and carry the nested objects on the stored JSONL records so scoped slices keep them. goose's input_tokens includes cache reads and writes, so the cache share is subtracted and reported separately, matching Entire's fresh-input convention.
An accumulated_usage object holding only total_tokens (or only cache figures) was treated as reported, so CalculateTokens returned zeros and never consulted usage. Treat a nested object as reported only when it carries an input or output count. Fields are still not mixed between accumulated_usage and usage, since they cover different spans.
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@peyton-alt flagging this since it hasn't had a look since Oct 28. It fixes #74 (goose 1.39+ token totals reading as zero). One question: CONTRIBUTING.md asks for |
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Fixes #74. This is assigned to me.
Why
Upstream issue 74: on current goose,
entire-agent-goose calculate-tokensreports zero tokens for every session. The adapter reads the flataccumulated_input_tokens/accumulated_output_tokensexport fields. goose stopped writing them in 1.39.0, whereSessiongained nestedusageandaccumulated_usageobjects instead. Nothing errors, so the zeros go unnoticed.What
CalculateTokensnow checks the token shapes in this order:accumulated_usage(the whole session, compacted history included), thenusage(the current context window), then the old flat fields for goose 1.38 and earlier.input_tokensoroutput_tokensvalue (a reported 0 counts). A missing object, goose's all-null defaultUsage, or one holding onlytotal_tokens/cache figures falls through to the next shape. Fields are never mixed betweenaccumulated_usageandusage, because they cover different spans (whole session vs current context window).cache_read_input_tokens→cache_read_tokensandcache_write_input_tokens→cache_creation_tokens.Usage.input_tokensas all prompt tokens, cache reads and writes included (token_usage.rs, 1.39.0+). Entire'sInputTokensmeans fresh input only, and Entire adds it to the cache fields. To avoid counting cache twice,input_tokensis reported asinput − cache_read − cache_write, never below 0. Using the issue's 1.46.0 numbers that givesin=14, not 124611. As a check, 14 + 112820 + 11777 + 394 = 125005, which matches goose's owntotal_tokens.usageandaccumulated_usageare added to the session header that gets stamped onto every stored JSONL record. Entire runscalculate-tokenson the stored transcript, sometimes on a line-sliced part of it, so the totals have to survive that.Tests (new
tokens_test.go)Cases:
accumulated_usagepreferred overusage;usageused whenaccumulated_usageis all null;usageused whenaccumulated_usageis absent; pre-1.39 flat fields; null cache fields; legitimately zero totals; no token fields at all; cache larger than input (input stays at 0); nested totals surviving the stored JSONL path and a scoped slice.Before (new tests run against unchanged
main):(The three cases that pass on
mainare regression guards: the legacy shape and the zero cases, whichmainalready handles correctly.)After: all 10 subtests and the existing
TestCalculateTokenspass.Binary smoke test,
calculate-tokens --offset 0on a 1.39+-shaped export:Checks (Go 1.26.0, golangci-lint 2.11.3; mise was not available, so I ran the commands mise would run):
gofmt -s -l .clean;golangci-lint runreports 0 issues in all 9 Go modules;go test ./...passes for every Go agent;go test -racepasses for goose; every Go agent builds;go vet -tags=e2epasses one2e/. Re-run later via mise with the pinned tools:lint:licensespasses, the crewai and langgraph wrapper tests pass, and the protocol-compliance suite at CI's pin gives 46 pass / 0 fail / 6 skip (same as main).Update (second commit): this tightens the fallback rule after review. An
accumulated_usageholding onlytotal_tokenspreviously returned zeros without checkingusage. There is a new test case for this; it failed before the change and passes after. In goose's own write path (record_usage_metrics, v1.46.0), oneUPDATEsets both objects from the same per-turn usage, so a field that is null inaccumulated_usageis also null inusage. That means this case most likely comes only from imported or externally written sessions.How I verified the goose shapes
I read goose's source at the release tags. I did not run goose.
Sessionhas flatinput_tokens,accumulated_input_tokens, and similar fields.Sessionhasusage: Usageandaccumulated_usage: Usagewith#[serde(default)].Usagehas fiveOption<i32>fields and noskip_serializing_if, so missing values come out asnull.goose session export --format jsonisserde_json::to_string_pretty(&session).input_tokensincludes cache has been intoken_usage.rssince 1.39.0.Not verified
goose session exportwas captured for this PR. The 1.46-shaped fixture uses the numbers from the issue, and its structure comes from goose source.mise run test:e2e), because it needs real goose andentire.Entire-Checkpointtrailer (see the note at the top).fromOffsetis still ignored, as before. goose only reports session-level totals.